﻿Imports System.ComponentModel
Imports System.IO
Imports System.Net
Imports System.Threading.Tasks
Imports System.Windows.Forms.VisualStyles.VisualStyleElement
Imports System.IO.Compression
Imports System.Net.Http
Imports System.Security.Cryptography
Imports System.Text
Imports System.Threading
Imports System.Xml

''' <summary>
''' 下载进度页面     
''' </summary>
''' <remarks></remarks>
Public Class DownloadProgress
    Inherits Form
    Private isDownloading As Boolean = False ' 标志位
    Private strlog As String
#Region "类申明变量"

    Private _appFrom1 As String
    Private _appName1 As String

#End Region

#Region "类重构"



#End Region

    Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        ' 开始检查更新和下载文件
        Await CheckForUpdateAndDownload()
    End Sub

#Region "窗体自定义函数及事件"

    Private Async Function GetServerVersionAsync() As Task(Of String)
        ' 更改后的服务器版本文件URL
        Dim versionUrl As String = ServerVersionUrl ' "http://191.168.76.190/update/version.txt"
        Using client As New WebClient()
            Return Await client.DownloadStringTaskAsync(versionUrl).ConfigureAwait(False)
        End Using
    End Function
    Private Async Function DownloadUpdateAsync(updateUrl As String, savePath As String) As Task
        If isDownloading Then
            ' 如果已经在下载，则不进行任何操作
            Return
        End If

        Try
            isDownloading = True ' 设置标志位为正在下载

            Using client As New WebClient()
                ' 获取远程文件大小，用于后续与本地文件大小对比
                Dim remoteFileSize As Long = Await GetRemoteFileSize(updateUrl)

                ' 添加事件处理器，以便在下载过程中报告进度以及完成时执行特定逻辑
                AddHandler client.DownloadProgressChanged, AddressOf UpdateDownloadProgress
                AddHandler client.DownloadFileCompleted, AddressOf UpdateDownloadCompleted

                ' 异步发起文件下载请求，使用ConfigureAwait(False)避免潜在的死锁问题
                Await client.DownloadFileTaskAsync(New Uri(updateUrl), savePath).ConfigureAwait(False)
                ' 下载完成后，检查本地文件大小
                Dim localFileSize As Long = New IO.FileInfo(savePath).Length

                ' 验证下载的文件大小是否与服务器上的文件大小匹配
                If localFileSize = remoteFileSize Then
                    WriteLog("下载成功并且文件大小匹配。")
                Else
                    WriteLog($"下载失败或文件大小不匹配。远程文件大小: {remoteFileSize}, 本地文件大小: {localFileSize}")
                    ' 这里可以根据业务需求选择抛出异常或者采取其他措施来处理这种情况
                End If
            End Using
        Finally
            ' 确保即使发生异常，也重置标志位
            isDownloading = False
            ' 移除事件处理器以防止泄露
            '   RemoveHandler client.DownloadProgressChanged, AddressOf UpdateDownloadProgress
            '   RemoveHandler client.DownloadFileCompleted, AddressOf UpdateDownloadCompleted
        End Try
    End Function
    Private Async Function GetRemoteFileSize(url As String) As Task(Of Long)
        ' 默认值表示未知大小，-1 可以用来标记未能正确获取文件大小的情况
        Dim fileSize As Long = -1

        Using client As New HttpClient()
            Try
                ' 创建一个HEAD请求，只获取响应头而不下载内容
                Dim request = New HttpRequestMessage(HttpMethod.Head, url)

                ' 发送异步HTTP请求并等待响应，同时确保仅读取响应头部
                Dim response = Await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(False)

                ' 确保HTTP响应状态码表示成功
                response.EnsureSuccessStatusCode()

                ' 尝试从响应头中获取Content-Length字段，它代表了文件大小（以字节为单位）
                If response.Content.Headers.TryGetValues("Content-Length", {fileSize}) Then
                    ' 如果有返回值，则将其转换为Long类型
                    If Long.TryParse(response.Content.Headers.GetValues("Content-Length").FirstOrDefault(), fileSize) Then
                        ' 成功解析了文件大小
                    End If
                End If
            Catch ex As Exception
                ' 捕获所有异常，并输出错误信息到控制台
                WriteLog($"获取文件大小时出错: {ex.Message}")
            End Try
        End Using

        ' 返回文件大小，如果无法获取则返回默认值-1
        Return fileSize
    End Function
    Private Async Function CheckForUpdateAndDownload() As Task
        Try
            ' 配置文件路径
            ' 获取当前执行文件的目录，并构建配置文件路径
            Dim configFilePath As String = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AutoUpdater.config")

            ' 创建一个XmlDocument对象并加载配置文件
            Dim xmlDoc As New XmlDocument()
            xmlDoc.Load(configFilePath)

            ' 读取根节点
            Dim rootNode As XmlNode = xmlDoc.DocumentElement

            ' 读取ServerUrl和UpdateFile值
            ServerVersionUrl = GetNodeValue(rootNode, "ServerUrl") 'version.txt服务器地址
            UpdateZipFile = GetNodeValue(rootNode, "UpdateZipFile") ''更新文件update.zip地址
            Dim updateFile As String = GetNodeValue(rootNode, "UpdateFileName")

            ' 查询特定的 XML 元素并添加到字典
            Dim strAppName As String = GetNodeValue(rootNode, "AppName")
            Dim strAppFrom As String = GetNodeValue(rootNode, "AppFrom")
            Dim strOpenAppName As String = GetNodeValue(rootNode, "OpenApp") ' 要检查的进程名

            ' 检查进程是否正在运行
            If IsProcessRunning(strOpenAppName) Then
                strlog=$"请先关闭 【{strAppName } 】， 然后再尝试更新。"
                MessageBox.Show(strlog, "进程正在运行", MessageBoxButtons.OK, MessageBoxIcon.Warning)
           
                End ' 终止更新过程
                WriteLog(strlog)
            End If
            ' 使用 VersionChecker 类检查更新
            Dim versionChecker As New VersionChecker()
            Dim strlocalversion As String = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "local_version.txt")

            Dim serverVersion As String = Await GetServerVersionAsync()
            Dim localVersion As String = GetLocalVersion()

            If Version.Parse(serverVersion) > Version.Parse(localVersion) Then
                lblAppName.Text = $"{strAppName},服务器最新版本:{versionChecker.NServerVersion}"
                lblAppFrom.Text = $"本地版本：{versionChecker.NLocalVersion}"
                '                .AppFrom =$"本地版本：{versionChecker.NLocalVersion}" 
                '                .AppName =$"{strAppName},服务器最新版本:{versionChecker.NServerVersion}"


                ' 如果有新版本，开始下载
                Dim updateUrl As String = UpdateZipFile ' "http://191.168.76.190/update/update.zip"

                ' 定义保存路径为当前应用程序路径下的update文件夹中的update.zip
                Dim basePath As String = AppDomain.CurrentDomain.BaseDirectory ' 获取当前程序的基路径
                Dim updateDirPath As String = Path.Combine(basePath, "update") ' 构建update文件夹路径
                Dim savePath As String = Path.Combine(updateDirPath, "update.zip") ' 构建最终保存路径

                ' 检查/update目录是否存在，如果不存在则创建它
                If Not Directory.Exists(updateDirPath) Then
                    Directory.CreateDirectory(updateDirPath)
                End If

                Await DownloadUpdateAsync(updateUrl, savePath)

                ' 解压更新包并覆盖旧文件
                ExtractAndUpdateFiles(savePath, basePath)
                ' 更新本地版本文件
                UpdateLocalVersionFile(serverVersion)

                WriteLog($"更新完成。旧版本: {localVersion} 新版本: {serverVersion}")

                ' 下载完成后，可以在此处解压并替换旧文件，然后启动新的可执行文件
                Dim strAppexe As String = $"{strOpenAppName}.exe"
                LaunchUpdatedProgram(strAppexe)
                WriteLog("更新完成并启动了新程序。")
                End
            Else
                strlog = $"用户已拥有最新版本,当前版本是{serverVersion}"
                WriteLog(strlog)
                LaunchUpdatedProgram($"{strOpenAppName}.exe")
                End
            End If
        Catch ex As Exception
            WriteLog($"Failed to check for updates: {ex.Message}")
        End Try
    End Function


    Private Function GetLocalVersion() As String
        ' 从应用程序配置文件、注册表或其它持久化存储中读取本地版本号
        ' 这里假设版本号保存在一个名为 "local_version.txt" 的文本文件中
        Dim localVersionPath As String = Path.Combine(Application.StartupPath, "local_version.txt")

        If File.Exists(localVersionPath) Then
            Return File.ReadAllText(localVersionPath).Trim()
        Else
            ' 如果没有找到本地版本号，可以假定为最低版本或者抛出异常
            Throw New FileNotFoundException("Local version file not found.")
        End If
    End Function


    Private Sub UpdateDownloadProgress(sender As Object, e As DownloadProgressChangedEventArgs)
        ' 更新进度条，确保从UI线程更新
        Me.Invoke(Sub() progressBarCurrent.Value = e.ProgressPercentage)
    End Sub

    Private Sub UpdateDownloadCompleted(sender As Object, e As AsyncCompletedEventArgs)
        ' 处理下载完成后的逻辑，确保从UI线程更新
        If Not e.Cancelled AndAlso e.Error Is Nothing Then
            Me.Invoke(Sub() MessageBox.Show("下载完毕."))
        Else
            Me.Invoke(Sub() MessageBox.Show("下载未成功."))
        End If
    End Sub

    Private Sub LaunchUpdatedProgram(filePath As String)
        Try
            Process.Start(filePath)
            Application.Exit()
        Catch ex As Exception
            MessageBox.Show("Failed to launch the updated program: " & ex.Message)
        End Try
    End Sub
    Private Sub ExtractAndUpdateFiles(zipPath As String, extractPath As String)
        Try
            ' 确保解压目录存在
            If Not Directory.Exists(extractPath) Then
                Directory.CreateDirectory(extractPath)
            End If

            ' 使用 ZipArchive 来处理 ZIP 文件
            Using archive As ZipArchive = ZipFile.OpenRead(zipPath)
                For Each entry As ZipArchiveEntry In archive.Entries
                    Dim fullPath As String = Path.Combine(extractPath, entry.FullName)

                    ' 如果是目录，则创建它
                    If String.IsNullOrEmpty(entry.Name) Then
                        Directory.CreateDirectory(Path.GetDirectoryName(fullPath))
                    Else
                        ' 如果文件存在则删除，以确保强制覆盖
                        If File.Exists(fullPath) Then
                            File.SetAttributes(fullPath, FileAttributes.Normal)
                            File.Delete(fullPath)
                        End If

                        ' 解压文件到指定路径
                        entry.ExtractToFile(fullPath)
                    End If
                Next
            End Using

            ' 成功解压后可以删除ZIP文件以节省空间（可选）
            If File.Exists(zipPath) Then
                Try
                    File.Delete(zipPath)
                    WriteLog("ZIP文件已成功删除")
                Catch ex As UnauthorizedAccessException
                    WriteLog("没有足够的权限删除文件: " & ex.Message)
                Catch ex As IOException
                    WriteLog("文件正在使用中或其他IO错误: " & ex.Message)
                Catch ex As Exception
                    WriteLog("发生未知错误: " & ex.Message)
                End Try
            Else
                MessageBox.Show("无法找到要删除的ZIP文件")
            End If

            ' 可能还需要重启应用程序以加载新文件
            '   Application.Restart()

        Catch ex As Exception
            MessageBox.Show("Failed to extract and update files: " & ex.Message)
        End Try
    End Sub
    Private Sub buttonOk_Click(sender As Object, e As EventArgs) Handles buttonOk.Click
        End
    End Sub
    ' 辅助方法：从指定的父节点中获取子节点的值
    Private Function GetNodeValue(parentNode As XmlNode, nodeName As String) As String
        Dim node As XmlNode = parentNode.SelectSingleNode(nodeName)
        Return If(node IsNot Nothing, node.InnerText, String.Empty)
    End Function
    ' 辅助函数用于检查进程是否存在
    Private Function IsProcessRunning(processName As String) As Boolean
        ' 获取所有具有指定名称的进程
        Dim processes As Process() = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processName))
        Return processes.Length > 0
    End Function

    ' 创建日志目录
    Private Sub InitializeLogDirectory()
        Dim logDirPath As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log")
        If Not Directory.Exists(logDirPath) Then
            Directory.CreateDirectory(logDirPath)
        End If
    End Sub

    ' 日志记录函数
    Private Sub WriteLog(message As String)
        Try
            ' 获取应用程序的根目录
            Dim basePath As String = AppDomain.CurrentDomain.BaseDirectory
            
            ' 构建相对路径到 'log' 文件夹
            Dim logPath As String = Path.Combine(basePath, "log")
            
            ' 检查并创建 'log' 文件夹（如果它不存在）
            If Not Directory.Exists(logPath) Then
                Directory.CreateDirectory(logPath)
            End If
            
            ' 假设你想按照日期命名日志文件，并在每个条目前添加时间戳
            Dim logFileName As String = DateTime.Now.ToString("yyyyMMdd") & ".log"
            Dim logFilePath As String = Path.Combine(logPath, logFileName)
            
            ' 准备要写入日志文件的信息，包括时间戳
            Dim logMessage As String = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}" & vbCrLf
            
            ' 写入日志信息
            Using writer As New StreamWriter(logFilePath, True)
                writer.Write(logMessage)
            End Using
            
            ' 或者可以使用 File.AppendAllText 简化代码
            ' File.AppendAllText(logFilePath, logMessage)

        Catch ex As Exception
            ' 处理可能出现的异常，例如磁盘满、权限问题等
            Console.WriteLine($"Failed to write log: {ex.Message}")
            ' 或者你可以选择将错误信息写入另一个位置，或者完全不处理，这取决于你的需求。
        End Try
    End Sub
    
' 计算文件的SHA256哈希值
    Private Function ComputeFileHash(filePath As String) As String
        Using hashAlgorithm As HashAlgorithm = SHA256.Create()
            Using fileStream As FileStream = File.OpenRead(filePath)
                Dim hashBytes As Byte() = hashAlgorithm.ComputeHash(fileStream)
                Return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant()
            End Using
        End Using
    End Function

' 从服务器获取文件的SHA256哈希值
    Private Async Function GetFileHashFromServer(fileUrl As String) As Task(Of String)
        ' 实现这个函数以从服务器获取指定文件的哈希值。
        ' 这通常意味着你需要向服务器发送请求以获得该信息。
        ' 以下是一个简单的示例，假设服务器提供了一个API端点返回文件的哈希值。
        ' 你需要根据实际情况调整此方法。
        Throw New NotImplementedException("请实现从服务器获取文件哈希的方法。")
    End Function
    ' 更新本地版本文件
    Private Sub UpdateLocalVersionFile(newVersion As String)
        Dim localVersionPath As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "local_version.txt")
        File.WriteAllText(localVersionPath, newVersion)
    End Sub
#End Region
#Region "窗体自定义属性及参数"
    ''' <summary>
    ''' 程序授权来源      
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property AppFrom As String
        Get
            Return _appFrom1
        End Get
        Set(value As String)
            _appFrom1 = value
        End Set
    End Property
    ''' <summary>
    ''' 程序名称      
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property AppName As String
        Get
            Return _appName1
        End Get
        Set(value As String)
            _appName1 = value
        End Set
    End Property

    ''' <summary>
    ''' 服务器server的version.txt文件路径
    ''' </summary>
    ''' <returns></returns>
    Public Property ServerVersionUrl As String
    ''' <summary>
    ''' 服务器zip压缩包地址
    ''' </summary>
    ''' <returns></returns>
    Public Property UpdateZipFile As String


#End Region
End Class